package priv.pfz.basedao.generator;

import com.google.common.collect.ImmutableMap;
import javassist.*;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import priv.pfz.basedao.apt.AptUtils;
import priv.pfz.basedao.info.FieldInfo;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;

/**
 * @author yyjxp
 * 2024/2/13 16:16
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BytecodeGenerator {

    private static final String METHOD_NAME = "getValueMap";
    private static final String INIT_TEMPLATE = "java.util.Map valueMap = new java.util.HashMap(${size});\n";
    private static final String ADD_TEMPLATE = "if ($1.${methodName}() != null) valueMap.put(\"${fieldName}\", $1.${methodName}());\n";
    private static final String RETURN_TEMPLATE = "return valueMap;\n";

    /**
     * 泛型擦除前
     * public static Map<String, Object> getValueMap(GoodsEntity entity) {
     *      Map<String, Object> valueMap = new HashMap<>(6);
     *      if (entity.getId() != null) valueMap.put("id", entity.getId());
     *      if (entity.getCreateTime() != null) valueMap.put("id", entity.getCreateTime());
     *      ...
     *      return valueMap;
     * }
     */
    public static Method getUtilMethod(Class<?> entityClass, List<FieldInfo> fieldInfos) {
        //若存在无getter的字段，则不生成工具方法
        if (fieldInfos.stream().anyMatch(f -> f.getGetter() == null)) {
            return null;
        }

        //判断工具类是否已经生成（同一Entity多个Mapper的情况）
        String classname = entityClass.getName() + "$BaseDaoUtil";
        Class<?> utilClass = getClass(classname);
        if (utilClass == null) {
            utilClass = createClass(classname, entityClass, fieldInfos);
        }
        return getMethod(utilClass, entityClass);
    }

    private static Class<?> getClass(String classname) {
        try {
            return Class.forName(classname);
        } catch (ClassNotFoundException e) {
            return null;
        }
    }

    private static Method getMethod(Class<?> utilClass, Class<?> entityClass) {
        try {
            return utilClass.getMethod(METHOD_NAME, entityClass);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    private static Class<?> createClass(String classname, Class<?> entityClass, List<FieldInfo> fieldInfos) {
        try {
            ClassPool cp = ClassPool.getDefault();
            CtClass ctClass = cp.makeClass(classname);

            CtClass returnType = cp.get(List.class.getName());
            CtClass[] paramTypes = new CtClass[]{cp.get(entityClass.getName())};
            CtMethod ctMethod = new CtMethod(returnType, METHOD_NAME, paramTypes, ctClass);
            ctMethod.setModifiers(Modifier.PUBLIC | Modifier.STATIC);

            StringBuilder methodBodySb = new StringBuilder("{");
            methodBodySb.append(AptUtils.fillTemplate(INIT_TEMPLATE, ImmutableMap.of(
                    "size", fieldInfos.size() + "")));
            for (FieldInfo fieldInfo : fieldInfos) {
                methodBodySb.append(AptUtils.fillTemplate(ADD_TEMPLATE, ImmutableMap.of(
                        "methodName", fieldInfo.getGetter().getName(),
                        "fieldName", fieldInfo.getFieldName())));
            }
            methodBodySb.append(RETURN_TEMPLATE).append("}");
            ctMethod.setBody(methodBodySb.toString());

            ctClass.addMethod(ctMethod);
            Class<?> utilClass = ctClass.toClass();
            ctClass.detach();
            return utilClass;
        } catch (CannotCompileException | IllegalArgumentException | NotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}
